package com.sld.integrate.service.impl;

import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.sld.integrate.domain.*;
import com.sld.integrate.mapper.RecDevCalculateMapper;
import com.sld.integrate.mapper.RecPlateAlgorithmMapper;
import com.sld.integrate.service.IDevWorkSequenceService;
import com.sld.integrate.service.IRecDevCalculateService;
import com.sld.integrate.utils.MessageQueueTaskManager;
import com.sld.integrate.vo.CalculateTuple;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author wugangwei
 * @since 2022-03-22
 */
@Slf4j
@Service
public class RecDevCalculateServiceImpl implements IRecDevCalculateService {

    public static final String CALC_TIMER_KEY = "list:calc_timer:";

    public static final Double DEFAULT_PRESSURE_FACTOR = 0.5;
    public static final Double DEFAULT_SPEED_FACTOR = 0.5;

    @Resource
    MessageQueueTaskManager queueTaskManager;

    @Resource
    RedisTemplate redisTemplate;

    @Resource
    IDevWorkSequenceService workSequenceService;

    @Resource
    RecDevCalculateMapper recDevCalculateMapper;

    @Resource
    RecPlateAlgorithmMapper plateAlgorithmMapper;

    @Override
    public CalculateTuple tupleAvgCalculate(List<RecDevMessage> list, Boolean filterValue) {
        List rotateSpeedList = new ArrayList<>();
        List humidityList = new ArrayList<>();
        List temperatureList = new ArrayList<>();
        List pressureList = new ArrayList<>();
        for (int i = 0; i < list.size(); i++) {
            RecDevMessage obj = list.get(i);
            double zs = obj.getRotateSpeed();
            double sd = obj.getHumidity();
            double wd = obj.getTemperature();
            double yl = obj.getPressure();

            rotateSpeedList.add(zs);
            humidityList.add(sd);
            temperatureList.add(wd);
            pressureList.add(yl);

        }
        double avgSpeed = collectionAvgCalculate(rotateSpeedList, filterValue);
        double avgHumidity = collectionAvgCalculate(humidityList, filterValue);
        double avgTemperature = collectionAvgCalculate(temperatureList, filterValue);
        double avgPressure = collectionAvgCalculate(pressureList, filterValue);

        return new CalculateTuple(avgPressure, avgSpeed, avgTemperature, avgHumidity, list);
    }

    @Override
    public void executeTimerCalculate(RecDevWorkSequence sequence, RecDevMessage recDevMessage) {
        String devId = recDevMessage.getDevId();
        // 要有sequence
        if (sequence == null) {
            log.error("【定时检测】devId={} sequence没有生成，不能进行定时坍损计算", devId);
            return;
        }
        //要有标定值
        RecDevWorkStandard calibration = workSequenceService.queryCalibrationValue(sequence.getId());
        if (calibration == null) {
            log.error("【定时检测】devId={} 标定值还没计算出来，不能继续向下执行", devId);
            return;
        }

        //准备7组数据，不够就丢掉。
        String key = CALC_TIMER_KEY + devId;
        redisTemplate.opsForList().leftPush(key, recDevMessage);
        Long ttl = redisTemplate.getExpire(key);
        if (ttl == -1) { // 新队列，必须设置过期90秒。
            redisTemplate.expire(key, 90, TimeUnit.SECONDS);
        }
        List list = redisTemplate.opsForList().range(key, 0, MessageConsumerServiceImpl.WINDOW_SIZE - 1);

        if (list.size() < MessageConsumerServiceImpl.WINDOW_SIZE) {
            log.info("【定时检测】 devId={}, 数据已累积{}, 不够7个，不计算。", devId, list.size());
        }

        CalculateTuple avgTuple = tupleAvgCalculate(list, true);
        log.info("【定时检测】devId={}, 均值Tuple={}", devId, JSONObject.toJSONString(avgTuple));
        //查询是否有自定义的算法参数
        QueryWrapper<RecPlateAlgorithm> query = Wrappers.query();
        RecPlateAlgorithm algorithm = plateAlgorithmMapper.selectOne(query.eq("plate_no", sequence.getPlateNo()));

        // 计算逻辑
        int value = doCompute(calibration, avgTuple, algorithm).intValue();
        saveCalculateResult(sequence, avgTuple, algorithm, value, "C");
    }

    @Override
    public int executeStaticCalculate(RecDevWorkSequence sequence, RecDevMessage recDevMessage) {
        int limitCount = MessageConsumerServiceImpl.WINDOW_SIZE;
        String devId = recDevMessage.getDevId();

        //标定值没有计算出来之前，不能计算这个！
        RecDevWorkStandard calibration = workSequenceService.queryCalibrationValue(sequence.getId());
        if (calibration == null) {
            log.info("【常规坍损计算】 devId={}标定值还没计算出来，不能继续向下执行", devId);
            return -1;
        }

        List list = queueTaskManager.gatherMessage(recDevMessage, limitCount);
        if (list == null || list.size() < limitCount) {
            log.info("【常规坍损计算】 devId={}, 数据已累积{}, 不够{}个，return -1。", devId, list.size(), limitCount);
            return -1;
        }

        CalculateTuple avgTuple = tupleAvgCalculate(list, true);
        log.info("【常规坍损计算】devId={}, 均值Tuple={}", devId, JSONObject.toJSONString(avgTuple));
        //查询是否有自定义的算法参数
        QueryWrapper<RecPlateAlgorithm> query = Wrappers.query();
        RecPlateAlgorithm algorithm = plateAlgorithmMapper.selectOne(query.eq("plate_no", sequence.getPlateNo()));

        // 计算逻辑
        int value = doCompute(calibration, avgTuple, algorithm).intValue();
        saveCalculateResult(sequence, avgTuple, algorithm, value, "A");
        return value;
    }

    @Override
    public void compensateCompute(String devId, List<RecDevMessage> messages) {
        log.info("【补偿计算】devId={}，list.size={}", devId, messages.size());
        //不做大小值过滤，直接求均值
        CalculateTuple avgTuple = tupleAvgCalculate(messages, false);
        log.info("【补偿计算】devId={}，均值={}", devId, JSONObject.toJSONString(avgTuple));
        //查询是否有自定义的算法参数
        RecPlateAlgorithm algorithm = plateAlgorithmMapper.queryOneByDevId(devId);
        RecDevWorkSequence sequence = workSequenceService.queryDevRecentlyWorkInfo(devId);
        if (sequence == null) {
            log.error("【补偿计算】无任务队列，思考为什么会有补偿？");
            return;
        }
        RecDevWorkStandard calibration = workSequenceService.queryCalibrationValue(sequence.getId());
        if (calibration == null) {
            log.error("【补偿计算】无标定值，思考为什么会有补偿？");
            return;
        }
        // 计算逻辑
        int value = doCompute(calibration, avgTuple, algorithm).intValue();
        saveCalculateResult(sequence, avgTuple, algorithm, value, "B");
    }

    @Override
    public void updateDestinationDetectValue(Integer workId, int timeOutSecond) {
        log.info("【工地检测数据回溯】workId={}", workId);
        QueryWrapper<RecDevCalculate> query = Wrappers.query();
        List<RecDevCalculate> list = recDevCalculateMapper.selectList(query.lambda()
                .eq(RecDevCalculate::getWorkId, workId)
                .ne(RecDevCalculate::getGroupType, "C")
                .orderByDesc(RecDevCalculate::getInsertTime));
        log.info("【工地检测数据回溯】workId={},去掉定时检测数据后，共{}条计算值", workId, list.size());
        if (list == null || list.size() == 0) {
            return;
        }

        List<RecDevCalculate> batchList = new ArrayList<>();
        RecDevCalculate current = list.get(0);

        batchList.add(current);
        for (int i = 1; i < list.size(); i++) {
            RecDevCalculate previous = list.get(i);
            long betweenMinute = DateUtil.between(current.getInsertTime(), previous.getInsertTime(), DateUnit.MINUTE);
            if (betweenMinute < timeOutSecond) {
                current = previous;
                batchList.add(current);
                log.info("【工地检测数据回溯】workId={},当前={}，上个={}符合2分钟回溯。", workId, current.getInsertTime(), previous.getInsertTime());
            } else {
                log.info("【工地检测数据回溯】workId={},当前={}，上个={}不符合2分钟回溯，终止回溯过程。", workId, current.getInsertTime(), previous.getInsertTime());
                break;
            }
        }
        log.info("【工地检测数据回溯】workId={},回溯记录共计{}条", workId, batchList.size());
        int count = recDevCalculateMapper.batchUpdateDestinationDetect(batchList);
        log.info("【工地检测数据回溯】workId={},更新回溯记录共计{}条", workId, count);
    }

    private void saveCalculateResult(RecDevWorkSequence sequence, CalculateTuple avgTuple, RecPlateAlgorithm algorithm, int value, String dataGroup) {
        RecDevCalculate calculate = new RecDevCalculate();
        calculate.setDevId(sequence.getDevId());
        calculate.setWorkId(sequence.getId());
        calculate.setWorkSequence(sequence.getWorkSequence());
        calculate.setPressure(avgTuple.getPressure().floatValue());
        calculate.setRotateSpeed(avgTuple.getSpeed().floatValue());
        calculate.setHumidity(avgTuple.getHumidity().floatValue());
        calculate.setTemperature(avgTuple.getTemperature().floatValue());
        calculate.setGroupType(dataGroup); //A常规停车检测 B 补偿 C定时
        if (algorithm != null) {
            calculate.setAlgorithmId(algorithm.getId());
        }
        String msgIds = avgTuple.getMessages().stream().map(item -> item.getMsgId().toString()).collect(Collectors.joining(","));
        calculate.setMsgIds(msgIds);
        calculate.setCalcResult(value);
        calculate.setInsertTime(new Date());
        recDevCalculateMapper.insert(calculate);
    }

    private Double doCompute(RecDevWorkStandard standard, CalculateTuple tuple, RecPlateAlgorithm algorithm) {
        // 丨V1-V2丨×A+丨P2-P1丨×B
        // V1 标定、V2 平均值、P1 压力标定、P2平均值、A为转速系数、B为压力系数
        Double pressureFactor = algorithm == null ? DEFAULT_PRESSURE_FACTOR : algorithm.getPressureFactor();
        Double speedFactor = algorithm == null ? DEFAULT_SPEED_FACTOR : algorithm.getSpeedFactor();
        double speedValue = Math.abs(standard.getSpeed() - tuple.getSpeed()) * speedFactor;
        double pressureValue = Math.abs(standard.getPressure() - tuple.getPressure()) * pressureFactor;

        double value = speedValue + pressureValue;
        log.info("【坍损计算】压力系数{},转速系数{}；标定P={},标定S={},均值P={},均值S={}. value={}", pressureFactor, speedFactor,
                standard.getPressure(), standard.getSpeed(), tuple.getPressure(), tuple.getSpeed(), value);
        return value;
    }

    private double collectionAvgCalculate(List<Double> list, Boolean filterValue) {
        double sum= list.stream().collect(Collectors.summingDouble(x-> x));
        if (!filterValue) {
            return sum / list.size();
        }
        double max= list.stream().max(Comparator.naturalOrder()).get();
        double min= list.stream().min(Comparator.naturalOrder()).get();
        return (sum - max - min) / (list.size() - 2);
    }
}
